home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Resource Library: Multimedia
/
Resource Library: Multimedia.iso
/
hypercrd
/
xcmds
/
rtf-redr.hqx
/
rtf.c
< prev
next >
Wrap
Text File
|
1992-12-19
|
15KB
|
636 lines
/*
* This software is copyright 1992 by Robert Morris.
* You may freely redistribute this software as shareware
* if you do so in the same form as you got it. If you find
* this software useful, please send $12 to:
* Robert Morris
* P.O. Box 1044
* Harvard Square Station
* Cambridge, MA 02238
* ecognome@aol.com
* If you incorporate any of this software in any kind of
* commercial product, please send $2 per copy distributed
* to the above address.
*/
/*
* utilities to help parse RTF.
*/
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <HyperXCmd.h>
#include "lists.h"
#include "rtf.h"
int sprintf(char *, ...);
extern int goterror;
void error(char *);
typedef struct state{
int font;
int face;
int size;
int dirty; /* do we need a new TE style before next char? */
int isfonttable; /* \fonttbl group */
int deffont; /* default rtf font # */
int invisible;
struct state *last;
} State;
State *pushstate(State *old), *popstate(State *);
struct ftable{
int rtf; /* rtf font # */
int mac; /* mac font # */
} **ftable;
int startstyle(MyStyleHandle sh, long start, int font, int face, int size);
void readcontrol(struct list *, char *word, char *arg);
void docontrol(struct list *inp, struct list *outp, State *state,
MyStyleHandle sh, char control[], char arg[]);
void appchars(struct list *outp, char *s, State *state, MyStyleHandle sh);
/*
* txt should be a handle to RTF text.
* puts a (partial) style handle into *outsh, and a text handle into *outtxt.
* if outsh is zero, don't bother with styles.
* the text handle is null-terminated.
*/
void
parsertf(Handle txt, MyStyleHandle *outsh, Handle *outtxt)
{
struct list out, in;
register int c;
STElement *stp;
MyStyleHandle sh;
register State *state;
if(outsh)
*outsh = 0;
*outtxt = 0;
out.h = 0;
state = 0;
sh = 0;
ftable = 0;
ftable = (struct ftable **)NewHandle(0L);
if(ftable == 0){
error("error : out of memory for ftable");
goto out;
}
if(outsh){
sh = NewMyStyleHandle();
if(sh == 0){
error("error : out of memory for sh");
goto out;
}
} else {
sh = 0;
}
state = pushstate((State *) 0);
if(state == 0){
error("error : out of memory for state");
goto out;
}
NewList(txt, &in);
NewList((char **) 0, &out);
while((c = ReadListChar(&in)) != -1 && c != '\0' && goterror == 0){
if(c == '\\'){
char control[256], arg[256];
readcontrol(&in, control, arg);
docontrol(&in, &out, state, sh, control, arg);
} else if(c == '{'){
state = pushstate(state);
if(state == 0){
error("error : out of memory for new state");
goto out;
}
} else if(c == '}'){
state = popstate(state);
if(state == 0){
error("error : unbalanced }");
goto out;
}
state->dirty = 1;
} else if(state->isfonttable == 0 &&
c != '\r' &&
c != '\n' &&
state->invisible == 0){
if(state->dirty){
startstyle(sh, out.ptr, state->font, state->face, state->size);
state->dirty = 0;
}
_AppendListChar(&out, c);
}
}
/* add dummy style to mark the end */
startstyle(sh, out.ptr, 0, 0, 12);
if(sh)
(*sh)->nRuns -= 1; /* nRuns doesn't count that last dummy run! */
if(goterror)
goto out;
TrimList(&out);
if(out.h == 0){
error("error : out of memory for output text");
goto out;
}
*outtxt = out.h;
out.h = 0;
if(outsh){
*outsh = sh;
sh = 0;
}
out:
if(ftable)
DisposHandle((Handle) ftable);
if(sh)
DisposMyStyleHandle(sh);
if(out.h)
DisposHandle(out.h);
while(state)
state = popstate(state);
}
int
startstyle(sh, start, font, face, size)
register MyStyleHandle sh;
long start;
int font, face, size;
{
register int si;
int run, err;
register STPtr st;
STPtr st1;
long newlen;
if(sh == 0)
return;
/* is there an existing style like this? */
st = 0;
for(si = 0; si < (*sh)->nStyles; si++){
st = (*((*sh)->styleTab)) + si;
if(st->stFont == font && st->stFace == face && st->stSize == size)
break;
}
if(si >= (*sh)->nStyles)
st = 0;
/* is the last style just like this one? */
if(st && (*sh)->nRuns > 0 &&
(*sh)->runs[(*sh)->nRuns-1].styleIndex == si)
return;
if(st == 0){
(*sh)->nStyles += 1;
SetHandleSize((Handle)((*sh)->styleTab),
(*sh)->nStyles * sizeof(STElement));
if((err = MemError()) != 0){
(*sh)->nStyles = 0;
SetHandleSize((Handle)((*sh)->styleTab), 0L);
error("error : out of memory for more styleTab");
return(err);
}
si = (*sh)->nStyles - 1;
st = (*((*sh)->styleTab)) + si;
st->stCount = 0;
st->stHeight = 12; /* ??? */
st->stAscent = 10; /* ??? */
st->stFont = font;
st->stFace = face;
st->stSize = size;
st->stColor.red = st->stColor.green = st->stColor.blue = 0;
}
st->stCount += 1;
if((*sh)->nRuns > 0 && (*sh)->runs[(*sh)->nRuns-1].startChar == start){
/* overwrite previous run, since it starts in the same place */
run = (*sh)->nRuns - 1;
st1 = (*((*sh)->styleTab)) + (*sh)->runs[run].styleIndex;
st1->stCount -= 1;
} else {
run = (*sh)->nRuns;
(*sh)->nRuns += 1;
newlen = (char *) &((*sh)->runs[(*sh)->nRuns]) - (char *) *sh;
SetHandleSize((Handle)sh, newlen);
if((err = MemError()) != 0){
(*sh)->nRuns -= 1;
error("error : out of memory for more runs");
return(err);
}
}
(*sh)->runs[run].startChar = start;
(*sh)->runs[run].styleIndex = si;
return(0);
}
/*
* a '\\' has been read; read the control word and any argument.
* if the following delimiter is a space, consume it; otherwise
* leave it alone.
*/
void
readcontrol(lp, word, arg)
struct list *lp;
char word[], arg[]; /* out parameters: the control word and argument */
{
int i, c;
word[0] = arg[0] = '\0';
i = 0;
while(i < 254 && isalpha(c = ReadListChar(lp)))
word[i++] = c;
word[i] = '\0';
if(i == 0){
switch(c){
case '\'':
/* \' followed by two hex digits */
word[0] = '\'';
word[1] = '\0';
arg[0] = ReadListChar(lp);
arg[1] = ReadListChar(lp);
arg[2] = '\0';
return;
default:
/* some one-character punctuation code */
word[0] = c;
word[1] = '\0';
return;
}
}
if(isdigit(c) || c == '-'){
/* we have a parameter */
i = 0;
do{
arg[i++] = c;
} while(i < 254 && isdigit(c = ReadListChar(lp)));
arg[i] = '\0';
}
if(c != ' ' && c != '\n' && c != '\r'){
/* give back the look-ahead character */
lp->ptr -= 1;
}
}
State *
pushstate(old)
State *old;
{
State *new;
new = (State *) NewPtr(sizeof(State));
if(new == 0){
while(old){
new = old->last;
DisposPtr((Ptr)old);
old = new;
}
return(0);
}
if(old){
*new = *old;
} else {
new->font = new->face = 0;
new->size = 12;
new->dirty = 1;
new->isfonttable = 0;
new->invisible = 0;
new->deffont = -1;
}
new->last = old;
return(new);
}
State *
popstate(st)
State *st;
{
State *last;
if(st){
last = st->last;
DisposPtr((Ptr)st);
return(last);
}
return(0);
}
void
docontrol(inp, outp, state, sh, control, arg)
struct list *inp, *outp;
State *state;
MyStyleHandle sh;
char control[], arg[];
{
int c;
if(strcmp(control, "fonttbl") == 0){
state->isfonttable = 1;
SetHandleSize((Handle)ftable, 0L);
} else if(strcmp(control, "f") == 0 && state->isfonttable){
int mac, rtf, i, nfonts;
char family[256], name[256];
rtf = atoi(arg);
c = ReadListChar(inp);
while(isspace(c))
c = ReadListChar(inp);
if(c == '\\'){
readcontrol(inp, family, name); /* read the family */
} else {
inp->ptr -= 1;
family[0] = '\0';
}
/* read the font name, up to a semicolon */
i = 0;
while(i < 254 && (c = ReadListChar(inp)) != -1 && c != 0 &&
c != ';' && c != '}' && c != '\\'){
if(i == 0 && isspace(c))
continue;
name[i++] = c;
}
name[i] = '\0';
CtoPstr(name);
GetFNum((StringPtr)name, &mac);
nfonts = GetHandleSize((Handle) ftable) / sizeof(**ftable);
SetHandleSize((Handle) ftable, sizeof(**ftable) * (nfonts + 1));
if(MemError() == 0){
(*ftable)[nfonts].rtf = rtf;
(*ftable)[nfonts].mac = mac;
} else
error("error : out of memory for more ftable");
} else if(strcmp(control, "f") == 0){
state->font = findrtffont(atoi(arg));
state->dirty = 1;
} else if(strcmp(control, "deff") == 0){
state->deffont = atoi(arg);
} else if(strcmp(control, "plain") == 0){
state->font = findrtffont(state->deffont);
state->face = 0;
state->size = 12; /* ??? */
state->dirty = 1;
state->invisible = 0;
} else if(strcmp(control, "b") == 0){
xface(state, bold, arg);
} else if(strcmp(control, "i") == 0){
xface(state, italic, arg);
} else if(strcmp(control, "outl") == 0){
xface(state, outline, arg);
} else if(strcmp(control, "shad") == 0){
xface(state, shadow, arg);
} else if(strcmp(control, "hcgroup") == 0){
xface(state, 0x80, arg); /* HyperCard grouped text */
} else if(strcmp(control, "v") == 0){
if(arg[0] == '0')
state->invisible = 0;
else
state->invisible = 1;
} else if(strcmp(control, "fs") == 0){
state->size = atoi(arg) / 2;
state->dirty = 1;
} else if(strcmp(control, "ul") == 0 || strcmp(control, "ulw") == 0 ||
strcmp(control, "uld") == 0 || strcmp(control, "uldb") == 0){
xface(state, underline, arg);
} else if(strcmp(control, "ulnone") == 0){
state->face &= ~underline;
state->dirty = 1;
} else if(strcmp(control, "~") == 0){
appchars(outp, "\xCA", state, sh); /* option-space */
} else if(strcmp(control, "_") == 0){
appchars(outp, "-", state, sh);
} else if(strcmp(control, "bullet") == 0){
appchars(outp, "Ñ", state, sh); /* option-8 */
} else if(strcmp(control, "ldblquote") == 0){
appchars(outp, "╥", state, sh); /* option-[ */
} else if(strcmp(control, "rdblquote") == 0){
appchars(outp, "╙", state, sh); /* option-shift-[ */
} else if(strcmp(control, "endash") == 0){
appchars(outp, "╨", state, sh); /* option-- */
} else if(strcmp(control, "emdash") == 0){
appchars(outp, "╤", state, sh); /* option-shift-- */
} else if(strcmp(control, "lquote") == 0){
appchars(outp, "╘", state, sh); /* option-] */
} else if(strcmp(control, "rquote") == 0){
appchars(outp, "╒", state, sh); /* option-shift-] */
} else if(strcmp(control, "'") == 0){
char bf[2];
sscanf(arg, "%x", &c);
bf[0] = c;
bf[1] = '\0';
appchars(outp, bf, state, sh);
} else if(strcmp(control, "line") == 0){
appchars(outp, "\r", state, sh);
} else if(strcmp(control, "tab") == 0){
appchars(outp, " ", state, sh);
} else if(strcmp(control, "colortbl") == 0 ||
strcmp(control, "pict") == 0 ||
strcmp(control, "footnote") == 0 ||
strcmp(control, "annotation") == 0 ||
strcmp(control, "header") == 0 ||
strcmp(control, "footer") == 0 ||
strcmp(control, "info") == 0 ||
strcmp(control, "field") == 0 ||
strcmp(control, "*") == 0){
flushgroup(inp);
} else if(strcmp(control, "stylesheet") == 0){
flushgroup(inp); /* seems to be OK to ignore */
} else if(strcmp(control, "par") == 0){
appchars(outp, "\r", state, sh);
} else if(isalnum(control[0]) == 0){
appchars(outp, control, state, sh);
}
}
void
appchars(outp, s, state, sh)
struct list *outp;
char *s;
State *state;
MyStyleHandle sh;
{
if(state->invisible == 0){
if(state->dirty){
startstyle(sh, outp->ptr, state->font, state->face, state->size);
state->dirty = 0;
}
AppendList(outp, s);
}
}
int
findrtffont(rtf)
{
int i, nfonts;
nfonts = GetHandleSize((Handle) ftable) / sizeof(**ftable);
for(i = 0; i < nfonts; i++){
if((*ftable)[i].rtf == rtf){
return((*ftable)[i].mac);
}
}
return(0);
}
xface(state, face, arg)
State *state;
int face;
char arg[];
{
if(arg[0] == '0')
state->face &= ~face;
else
state->face |= face;
state->dirty = 1;
}
flushgroup(inp)
struct list *inp;
{
int c, depth;
depth = 0;
while((c = ReadListChar(inp)) != -1 && c != 0){
if(c == '}'){
if(depth <= 0){
inp->ptr -= 1; /* allow } to pop the state */
break;
}
--depth;
} else if(c == '{'){
depth++;
}
}
}
MyStyleHandle
NewMyStyleHandle(void)
{
MyStyleHandle sh;
sh = (MyStyleHandle) NewHandle(sizeof(**sh));
if(sh == 0)
return(0);
(*sh)->nRuns = 0;
(*sh)->nStyles = 0;
(*sh)->styleTab = (STHandle) NewHandle(0L);
if((*sh)->styleTab == 0){
DisposHandle(sh);
return(0);
}
return(sh);
}
void
DisposMyStyleHandle(MyStyleHandle sh)
{
if(sh){
if((*sh)->styleTab)
DisposHandle((*sh)->styleTab);
(*sh)->styleTab = 0;
DisposHandle(sh);
}
}
/*
* convert one of my style handles into a TextEdit TEStyleHandle.
* since my style handles can deal with > 32K of text, *start
* indicates where to start converting. this routine leaves the
* first unconverted character position in *start when it returns.
* it tries not to split lines. it modifies an existing style handle.
* max specifies how many characters can be put in a field.
* txt is the total txt (ie might be > 32K).
* remember that (*sh)->nRuns doesn't count that last dummy run.
*/
OSErr
CvtMyStyleHandle(MyStyleHandle sh1, long *start, TEStyleHandle sh2, long max,
Handle txt)
{
STHandle st;
long firstrun, lastrun, run, txtlen, len, i;
OSErr err;
txtlen = GetHandleSize(txt);
if(*start + max > txtlen)
max = txtlen - *start;
/* shrink max soas to break at a return */
if(*start + max < txtlen){
for(i = max-1 ; i >= 0; --i)
if((*txt)[*start + i] == '\r')
break;
if(i > 0)
max = i + 1;
}
/* find the first relevant run */
for(run = 0; run < (*sh1)->nRuns; run++)
if((*sh1)->runs[run].startChar <= *start &&
(*sh1)->runs[run + 1].startChar > *start)
break;
firstrun = run;
/* find the last relevant run: it must be within max of *start */
for(run = firstrun; run <= (*sh1)->nRuns; run++)
if((*sh1)->runs[run].startChar >= *start + max)
break;
lastrun = run - 1;
/*
* allocate enough space in sh2's tables.
* also copies sh1's styleTab. this might be a bit
* wasteful if any are unused, but it means that
* the same indices can be used in sh2 as in sh1.
*/
(*sh2)->nRuns = lastrun - firstrun + 1;
(*sh2)->nStyles = (*sh1)->nStyles;
st = (*sh1)->styleTab;
HandToHand(&st);
(*sh2)->styleTab = st;
SetHandleSize(sh2, 32L + ((*sh2)->nRuns + 1) * sizeof(StyleRun));
if((err = MemError()) != 0)
return(err);
/* copy the relevant runs, adjusting the character pointers */
for(run = firstrun; run <= lastrun; run++){
(*sh2)->runs[run - firstrun].startChar = (*sh1)->runs[run].startChar - *start;
if((*sh2)->runs[run - firstrun].startChar < 0)
(*sh2)->runs[run - firstrun].startChar = 0;
(*sh2)->runs[run - firstrun].styleIndex = (*sh1)->runs[run].styleIndex;
}
if(lastrun < (*sh1)->nRuns)
len = (*sh1)->runs[lastrun + 1].startChar - *start;
else
len = txtlen - *start;
if(len > max)
len = max;
(*sh2)->runs[(*sh2)->nRuns].startChar = len;
*start += len;
return(0);
}